#ifndef _MS2SIM_H_
#define _MS2SIM_H_

#include <stdio.h>

#include <clx/libECM.h>
#include <clx/ecm_list.h>

#include "libms2sim.h"

#include "blockcache.h"

#define ERR(fmt, ...)	ECMERR("ERR: ms2sim: " fmt, ## __VA_ARGS__)

/** It's enough */
#define USEC_MAX	1000000
#define TS_MAX		(ecm_uint64)(-1)

extern ecm_uint32	duration;

extern ecm_double	arrival_rate;

extern msl_sesslen_type_t	sesslen_type;
extern msl_istime_type_t	istime_type;

extern ecm_uint32	n_medias;
extern ecm_uint32	max_media_length;
extern ecm_double	skew;

extern ecm_uint32	cache_blksize;
extern ecm_uint32	n_caches;

extern char		*bcache_mod;
extern char		*disksim_param;
extern ecm_uint32	disksim_raid;

extern ECMBOOL		disksim_enabled;
extern ECMBOOL		ignore_loss_violation_onstart;
extern ecm_double	allowed_loss_ratio;
extern ECMBOOL		allowed_loss_ratio_violated;

extern ecm_uint32	disk_blksize;
extern ecm_uint64	disk_BW;
extern ecm_uint32	disk_seektime;

extern ecm_uint64	network_BW;

extern ecm_uint64	ts_subsys;

extern ECMBOOL		is_stat_started;
extern ECMBOOL		is_stat_from_start;
extern ecm_uint64	ts_stat_start;
extern ecm_uint32	stat_duration;

extern blockcache_op_t	*bcache_op;

extern char	*disk_usage_trace;

typedef enum {
	STATE_IDLE,
	STATE_DISKIO,
	STATE_LOADED,
	STATE_DONE,
} session_state_t;

typedef struct {
	ecm_uint32	mid;
	char		*name;
	ecm_uint64	size;
	ecm_uint32	length;
	ecm_uint32	bid_max;
	ecm_uint64	bitrate;

	msl_iastat_t	iastat;

	ecm_uint32	n_requested;
} media_t;

typedef struct {
	ecm_uint32	sid;

	/** time step (microsec) */
	ecm_uint64	ts;
	ecm_uint64	ts_sesslen;
	ecm_uint64	ts_seek;

	ecm_int32	seekdist;

	/** session playing position in microsec time from 0 at beginning */
	ecm_uint64	usec_playing_pos;

	session_state_t	state;
	ecm_uint32	refcnt;

	ecm_uint64	ts_deadline;
	ecm_uint32	usec_round;
	ecm_uint32	bytes_round;

	ecm_uint32	bytes_buffered_client;
	ecm_uint32	bytes_buffered;

	/** my buffer offset */
	ecm_uint32	offset_buff;

	ecm_uint32	n_io_blks;
	bcache_t	**bcaches;

	/** Current media position as logical block index */
	ecm_uint32	bid;

	///TODO Need RTP Packet lost
	ecm_uint32	n_lost_rounds;
	ecm_uint32	n_rounds;

	media_t		*media;
	struct list_head	list;
	struct list_head	list_accepted;
} session_t;

typedef enum {
	SUBSYS_TYPE_NONE = 0,
	SUBSYS_TYPE_STAT,
	SUBSYS_TYPE_REQUEST,
	SUBSYS_TYPE_SERVICE,
	SUBSYS_TYPE_DISK,
	SUBSYS_TYPE_NETWORK,
	SUBSYS_TYPE_MAX = SUBSYS_TYPE_NETWORK
} subsys_type_t;

extern char	*logbasepath;
extern char	*confpath;
extern const char	*bcache_mgmt_name;

extern ECMBOOL going_to_shutdown;

extern void runsim(void);

/* Block Cache */
extern ECMBOOL init_bcache(void);
extern bcache_t *get_bcache(session_t *session, ecm_uint32 bid);
extern void put_bcache(session_t *session, bcache_t *bcache);
extern void complete_bcache_IO(bcache_t *bcache, ecm_uint64 ts);

extern void notify_session_leave(session_t *session);

extern void check_bcache_stats(void);
extern void report_bcache_result(void);

/* Media */
extern ECMBOOL init_media_pre(void);
extern ECMBOOL init_media(void);
extern ECMBOOL add_media(sr_t *sr_media);
extern media_t *access_media(ecm_uint64 ts, ecm_uint32 mid);

extern void check_media_stats(void);
extern void report_media_result(void);

/* Request */
extern ECMBOOL init_request(void);

extern void check_request_stats(void);
extern void report_request_result(void);

extern ecm_uint64 probe_request_system(ecm_uint64 ts);
extern void schedule_request_system(ecm_uint64 ts);

/* Session */
extern ECMBOOL init_session(void);
extern session_t *create_session(ecm_uint64 ts, ecm_uint32 mid);
extern void free_session(session_t *session);

extern void set_session_state(session_t *session, session_state_t state);
extern void try_to_seek_session(session_t *session, ecm_uint64 ts);

extern void put_session(session_t *session);

extern void run_sub_systems(void);

extern ecm_double calc_loss_ratio(void);

/* Service Subsystem */
extern ecm_uint64 probe_service_system(ecm_uint64 ts);
extern void schedule_service_system(ecm_uint64 ts);

extern void add_service_session(session_t *session);

extern void check_session_stats(ecm_uint64 ts);
extern void report_session_result(void);

/* Disk Subsystem */
extern ECMBOOL init_disk(void);

extern void invoke_disk_IO(bcache_t *bcache, ecm_uint64 ts);

extern ecm_uint64 probe_disk_system(ecm_uint64 ts);
extern void schedule_disk_system(ecm_uint64 ts);

extern void check_disk_stats(void);
extern void report_disk_result(void);

/* Disksim */
extern ECMBOOL init_disksim(void);
extern ecm_uint64 invoke_disksim(ecm_uint64 ts, ecm_uint32 mid, ecm_uint32 bid);

/* Network Subsystem */
extern ECMBOOL init_network(void);

extern ECMBOOL invoke_network_IO(session_t *session);

extern ecm_uint64 probe_network_system(ecm_uint64 ts);
extern void schedule_network_system(ecm_uint64 ts);

extern void check_network_stats(void);
extern void report_network_result(void);

/* Statistics */
extern ECMBOOL init_stat(void);
extern ecm_uint64 probe_stat_system(ecm_uint64 ts);
extern void schedule_stat_system(ecm_uint64 ts);

/* Configuration */
extern ECMBOOL init_conf_pre(void);
extern ECMBOOL init_conf(void);
extern sr_t *get_mod_sr_conf(const char *modname);

/* Logging */
extern FILE *open_logfile(const char *basepath, char **plogpath);
extern ECMBOOL setup_logging(void);
extern void cleanup_logging(void);
extern void printlog(const char *fmt, ...);

#ifdef MSM_DEBUG

extern ecm_dbg_t	ms2sim_dbg;

#define DBG_ALL		0xffff
#define DBG_MS2SIM	0x0001
#define DBG_REQUEST	0x0002
#define DBG_SERVICE	0x0004
#define DBG_DISK	0x0008
#define DBG_NETWORK	0x0010
#define DBG_SESSION	0x0020

#define DBG(dbgset, fmt, ...)	ECMDBG(ms2sim_dbg, dbgset, fmt, ## __VA_ARGS__)

const char *dbg_timestep(ecm_uint64 ts);
const char *dbg_session(session_t *session);

#else

#define DBG(dbgset, fmt, ...)	do {} while (0)

#endif

#endif
